Conjunto de datos de diágnostico de cáncer de mama en Wisconsin.
1. Análisis Inicial y Preprocesamiento de los Datos.
Para comenzar nuestro análisis es necesario realizar la carga de los datos:
data <- read.csv("data/data.csv")
data
NA
Instalamos los paquetes que serán necesarios durante nuestro proyecto:
#install.packages("tidyverse")
#install.packages("caret")
#install.packages("DataExplorer")
#install.packages("dplyr")
#install.packages("ggplot2")
#install.packages("lattice")
#install.packages("psych")
#install.packages("corrplot")
#install.packages("ggcorrplot")
library(caret)
Warning: package ‘caret’ was built under R version 4.4.2Loading required package: ggplot2
Warning: package ‘ggplot2’ was built under R version 4.4.2Loading required package: lattice
library(DataExplorer)
Warning: package ‘DataExplorer’ was built under R version 4.4.2Registered S3 method overwritten by 'htmlwidgets':
method from
print.htmlwidget tools:rstudio
library()
library(dplyr)
Warning: package ‘dplyr’ was built under R version 4.4.2
Attaching package: ‘dplyr’
The following objects are masked from ‘package:stats’:
filter, lag
The following objects are masked from ‘package:base’:
intersect, setdiff, setequal, union
library(ggplot2)
library(tidyverse)
── Attaching core tidyverse packages ─────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ lubridate 1.9.3 ✔ tibble 3.2.1
✔ purrr 1.0.2 ✔ tidyr 1.3.1
✔ readr 2.1.5 ── Conflicts ───────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
✖ purrr::lift() masks caret::lift()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(lattice)
library(psych)
Warning: package ‘psych’ was built under R version 4.4.2
Attaching package: ‘psych’
The following objects are masked from ‘package:ggplot2’:
%+%, alpha
library(corrplot)
Warning: package ‘corrplot’ was built under R version 4.4.2corrplot 0.95 loaded
library(ggcorrplot)
Warning: package ‘ggcorrplot’ was built under R version 4.4.2
Hagamos una vista inicial de los datos:
#primeras filas de nuestro dataset:
head(data)
# Dimensión de nuestro dataset:
dim(data)
[1] 569 33
Nuestro dataset cuenta con 33 columnas y 569 filas.
# Con str mostramos la estructura de nuestro dataframe, incluyendo los tipos de nuestras variables:
str(data)
'data.frame': 569 obs. of 33 variables:
$ id : int 842302 842517 84300903 84348301 84358402 843786 844359 84458202 844981 84501001 ...
$ diagnosis : chr "M" "M" "M" "M" ...
$ radius_mean : num 18 20.6 19.7 11.4 20.3 ...
$ texture_mean : num 10.4 17.8 21.2 20.4 14.3 ...
$ perimeter_mean : num 122.8 132.9 130 77.6 135.1 ...
$ area_mean : num 1001 1326 1203 386 1297 ...
$ smoothness_mean : num 0.1184 0.0847 0.1096 0.1425 0.1003 ...
$ compactness_mean : num 0.2776 0.0786 0.1599 0.2839 0.1328 ...
$ concavity_mean : num 0.3001 0.0869 0.1974 0.2414 0.198 ...
$ concave.points_mean : num 0.1471 0.0702 0.1279 0.1052 0.1043 ...
$ symmetry_mean : num 0.242 0.181 0.207 0.26 0.181 ...
$ fractal_dimension_mean : num 0.0787 0.0567 0.06 0.0974 0.0588 ...
$ radius_se : num 1.095 0.543 0.746 0.496 0.757 ...
$ texture_se : num 0.905 0.734 0.787 1.156 0.781 ...
$ perimeter_se : num 8.59 3.4 4.58 3.44 5.44 ...
$ area_se : num 153.4 74.1 94 27.2 94.4 ...
$ smoothness_se : num 0.0064 0.00522 0.00615 0.00911 0.01149 ...
$ compactness_se : num 0.049 0.0131 0.0401 0.0746 0.0246 ...
$ concavity_se : num 0.0537 0.0186 0.0383 0.0566 0.0569 ...
$ concave.points_se : num 0.0159 0.0134 0.0206 0.0187 0.0188 ...
$ symmetry_se : num 0.03 0.0139 0.0225 0.0596 0.0176 ...
$ fractal_dimension_se : num 0.00619 0.00353 0.00457 0.00921 0.00511 ...
$ radius_worst : num 25.4 25 23.6 14.9 22.5 ...
$ texture_worst : num 17.3 23.4 25.5 26.5 16.7 ...
$ perimeter_worst : num 184.6 158.8 152.5 98.9 152.2 ...
$ area_worst : num 2019 1956 1709 568 1575 ...
$ smoothness_worst : num 0.162 0.124 0.144 0.21 0.137 ...
$ compactness_worst : num 0.666 0.187 0.424 0.866 0.205 ...
$ concavity_worst : num 0.712 0.242 0.45 0.687 0.4 ...
$ concave.points_worst : num 0.265 0.186 0.243 0.258 0.163 ...
$ symmetry_worst : num 0.46 0.275 0.361 0.664 0.236 ...
$ fractal_dimension_worst: num 0.1189 0.089 0.0876 0.173 0.0768 ...
$ X : logi NA NA NA NA NA NA ...
# Con Describe podemos ver un primer resumen estadístico básico:
describe(data)
Warning: no non-missing arguments to min; returning InfWarning: no non-missing arguments to max; returning -Inf
# Tipos de datos en nuestras variables:
sapply(data, class)
id diagnosis radius_mean texture_mean
"integer" "character" "numeric" "numeric"
perimeter_mean area_mean smoothness_mean compactness_mean
"numeric" "numeric" "numeric" "numeric"
concavity_mean concave.points_mean symmetry_mean fractal_dimension_mean
"numeric" "numeric" "numeric" "numeric"
radius_se texture_se perimeter_se area_se
"numeric" "numeric" "numeric" "numeric"
smoothness_se compactness_se concavity_se concave.points_se
"numeric" "numeric" "numeric" "numeric"
symmetry_se fractal_dimension_se radius_worst texture_worst
"numeric" "numeric" "numeric" "numeric"
perimeter_worst area_worst smoothness_worst compactness_worst
"numeric" "numeric" "numeric" "numeric"
concavity_worst concave.points_worst symmetry_worst fractal_dimension_worst
"numeric" "numeric" "numeric" "numeric"
X
"logical"
# Veamos si existen valores faltantes en nuestros datos:
anyNA(data)
[1] TRUE
#Contamos el numero de valores faltantes por columna:
colSums(is.na(data))
id diagnosis radius_mean texture_mean
0 0 0 0
perimeter_mean area_mean smoothness_mean compactness_mean
0 0 0 0
concavity_mean concave.points_mean symmetry_mean fractal_dimension_mean
0 0 0 0
radius_se texture_se perimeter_se area_se
0 0 0 0
smoothness_se compactness_se concavity_se concave.points_se
0 0 0 0
symmetry_se fractal_dimension_se radius_worst texture_worst
0 0 0 0
perimeter_worst area_worst smoothness_worst compactness_worst
0 0 0 0
concavity_worst concave.points_worst symmetry_worst fractal_dimension_worst
0 0 0 0
X
569
plot_missing(data)
Como puede observarse, contamos con 569 valores faltantes en la última columna “X”. Más adelante veremos como tratarlo.
1.2 Análisis exploratorio de los datos
En este paso nuestro objetivo será entender la distribución y relaciones de variables.
Visualización de distribuciones y correlaciones:
#Veamos un resumen gráfico general:
plot_intro(data)
#Variables Categóricas:
plot_bar(data)
#Variables Numéricas
plot_histogram(data)
1.3 Preprocesamiento de los datos.
Eliminación de valores faltantes:
Para comenzar, ya sabemos que existe una columna cuyos valores son todos NA, es decir, faltantes. El primero paso en nuestro preprocesamiento será eliminar esta columna “x”:
data <- read.csv("data/data.csv")
data
head(data)
data <- data %>% select(-X)
Veamos si se ha borrado correctamente la columna X y si ahora existe algún otro valor faltante:
colnames(data)
[1] "id" "diagnosis" "radius_mean"
[4] "texture_mean" "perimeter_mean" "area_mean"
[7] "smoothness_mean" "compactness_mean" "concavity_mean"
[10] "concave.points_mean" "symmetry_mean" "fractal_dimension_mean"
[13] "radius_se" "texture_se" "perimeter_se"
[16] "area_se" "smoothness_se" "compactness_se"
[19] "concavity_se" "concave.points_se" "symmetry_se"
[22] "fractal_dimension_se" "radius_worst" "texture_worst"
[25] "perimeter_worst" "area_worst" "smoothness_worst"
[28] "compactness_worst" "concavity_worst" "concave.points_worst"
[31] "symmetry_worst" "fractal_dimension_worst"
# Veamos si existen valores faltantes en nuestros datos:
anyNA(data)
[1] FALSE
#Contamos el numero de valores faltantes por columna:
colSums(is.na(data))
id diagnosis radius_mean texture_mean
0 0 0 0
perimeter_mean area_mean smoothness_mean compactness_mean
0 0 0 0
concavity_mean concave.points_mean symmetry_mean fractal_dimension_mean
0 0 0 0
radius_se texture_se perimeter_se area_se
0 0 0 0
smoothness_se compactness_se concavity_se concave.points_se
0 0 0 0
symmetry_se fractal_dimension_se radius_worst texture_worst
0 0 0 0
perimeter_worst area_worst smoothness_worst compactness_worst
0 0 0 0
concavity_worst concave.points_worst symmetry_worst fractal_dimension_worst
0 0 0 0
plot_missing(data)
Además de la columna con valores Faltantes, también tenemos una columna “ID” que no nos aporta ninguna información por lo que también procederemos a eliminarla:
data <- data %>% select(-id)
colnames(data)
[1] "diagnosis" "radius_mean" "texture_mean"
[4] "perimeter_mean" "area_mean" "smoothness_mean"
[7] "compactness_mean" "concavity_mean" "concave.points_mean"
[10] "symmetry_mean" "fractal_dimension_mean" "radius_se"
[13] "texture_se" "perimeter_se" "area_se"
[16] "smoothness_se" "compactness_se" "concavity_se"
[19] "concave.points_se" "symmetry_se" "fractal_dimension_se"
[22] "radius_worst" "texture_worst" "perimeter_worst"
[25] "area_worst" "smoothness_worst" "compactness_worst"
[28] "concavity_worst" "concave.points_worst" "symmetry_worst"
[31] "fractal_dimension_worst"
head(data)
NA
Como podemos observar, se ha eliminado la columna “id” y se ha verificado que no existen mas valores faltantes exceptos los ya eliminados en “X”.
Después de ellos contamos con un dataset de:
dim(data)
[1] 569 31
569 filas y 31 columnas.
Codificación de variables Categóricas
Anteriormente vimos que nuestro dataset cuenta con una única columna de valores categóricos. “Diagnosis” cuyos valores tienen el siguiente significado: + M (malignant) + B (benign)
El siguiente paso en el preprocesado de datos será pasar esta columna a numérica. Como solo se presentan dos posibles valores (M y B), se aplicará Codificación Binaria: El valor “M” pasará a ser 1 y valor “B” pasará a ser 0.
Aunque más adelante volveremos a pasarla a categórica, ahora es necesario hacerla binaria para poder estudiar la correlación de las variables.
#M -> 1; B -> 0
data$diagnosis <- ifelse(data$diagnosis == "M", 1, 0)
Vamos a verificar que se ha realizado correctamente la conversión:
table(data$diagnosis)
0 1
357 212
print(data$diagnosis)
[1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 0
[50] 0 0 0 0 1 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 1 0 1 1 0 0 0 1 1 0 1 1 1 0 0 0 1 0 0 1 1 0 0
[99] 0 1 1 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 1 1 1 0 1 1 0 0 0 1 1 0 1 0 1 1 0 1 1 0 0 1 0 0 1 0 0 0 0 1
[148] 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 1 0 0 1 1 0 0 1 1 0 0 0 0 1 0 0 1 1 1 0 1 0 1 0 0 0 1 0 0 1 1 0
[197] 1 1 1 1 0 1 1 1 0 1 0 1 0 0 1 0 1 1 1 1 0 0 1 1 0 0 0 1 0 0 0 0 0 1 1 0 0 1 0 0 1 1 0 1 0 0 0 0 1
[246] 0 0 0 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 0 1 0 0 1 0 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0
[295] 0 0 0 1 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 0 1 1 1 0 0 0 0 1 0 1 0 1 0 0 0
[344] 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 1 1 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0
[393] 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 1 0 0 0 0 0
[442] 1 0 0 1 0 1 0 0 1 0 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 1
[491] 0 0 1 0 0 0 0 0 1 1 0 1 0 1 0 0 0 0 0 1 0 0 1 0 1 0 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 0
[540] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0
Nos cercioramos de que no existe ningún otro valor categórico en el dataset:
categorical_columns <- sapply(data, is.factor) | sapply(data, is.character)
names(data)[categorical_columns]
character(0)
Efectivamente, todas nuestras columnas ahora son numéricas, lo que nos da paso al siguiente punto en nuestro preprocesamiento de datos.
Estudio de correlación.
Al tener nuestro dataset limpio de NA y solo presentes variables numéricas, el siguiente paso será estudiar las posibles correlaciones de nuestro dataset.
Estudiar la correlación de los datos ayuda a identificar patrones y relaciones entre variables, lo que podría conducir a nuevas hipótesis y descubrimientos. Además, un buen estudio de correlaciones podría ser útil para seleccionar variables relevantes y construir modelos en un futuro.
Los valores que obtendremos tendrán la siguiente interpretación:
# Graficar la matriz de correlación con valores en las celdas
ggcorrplot(correlation_matrix,
method = "circle", # Utilizar círculos para representar correlaciones
type = "lower", # Mostrar solo la mitad inferior
lab = TRUE, # Añadir los valores de correlación
lab_size = 2, # Tamaño del texto en las celdas
colors = c("blue", "white", "red"), # Colores para las correlaciones negativas, neutrales y positivas
title = "Matriz de Correlación", # Título del gráfico
tl.cex = 10) # Tamaño de las etiquetas
Como puede observarse, al tratarese un conjunto de datos con 31 variables, la matriz de correlación cuesta interpretarla.
Por ello, como “diagnosis” es nuestra variable objetivo, vamos a observar cómo se correlacionan las demás variables con ella:
# Calcular la correlación entre cada variable numérica y 'diagnosis'
cor_with_target <- cor(data, data$diagnosis, use = "complete.obs")
# Crear un data frame para ver las correlaciones junto con los nombres de las variables
correlation_df <- data.frame(Variable = names(data), Correlation = cor_with_target)
# Ordenar el data frame por la columna de Correlation en orden descendente
correlation_df_sorted <- correlation_df[order(-correlation_df$Correlation), ]
# Ver las correlaciones ordenadas junto con los nombres de las variables
print(correlation_df_sorted)
NA
NA
En esta tabla podemos ver la correlación de cada variable con “diagnosis” en orden descendente.
Selección de Atributos:
Una de las formas de hacer una correcta selección de atributos es inspeccionar la anterior tabla de correlación:
Como puede observarse, no existe ninguna variable que supere el 0,8, cuando las variables se correlacionan con la variable clase, en nuestro caso diagnosis, con más de un 0,9, suele ser conveniente eliminarlas para evitar redundancia, lo que no es nuestro caso.
Por otro lado, tampoco tenemos valores demasiado bajos ni negativos, los valores negativos en nuestro caso, podrían ayudar al entrenamiento de nuestro modelo, es por ello, que de momento no se eliminarán variables del dataset.
2. Análisis Supervisado.
El aprendizaje supervisado, también conocido como machine learning supervisado, es una subcategoría del machine learning y la inteligencia artificial. Se define por el uso de conjuntos de datos etiquetados para entrenar algoritmos que clasifican los datos o predicen los resultados con precisión.
A medida que se introducen datos en el modelo, éste ajusta sus ponderaciones hasta que el modelo se ha ajustado adecuadamente, lo que ocurre como parte del proceso de validación cruzada.
En nuestro análisis, la variable a predecir será “diagnosis”, que como vimos anteriormente, es de tipo binario, tomando 1 cuando el tumor es maligno, y 0 cuando el tumor resulta benigno.
En este análisis se han evaluado diferentes modelos de clasificación para un conjunto de datos binario con 569 muestras y 30 predictores, clasificados en dos categorías: ‘Negative’ y ‘Positive’. Los modelos analizados son el Árbol de Decisión (CART), Random Forest (Bosque Aleatorio), Máquinas de Soporte Vectorial con núcleo Radial (SVM), y el Modelo Lineal Generalizado (GLM).
El primer paso para el análisis supervisado es realizar la divión del dataset.
2.1 División del Dataset:
Para este paso, usaremos la validación cruzada k-fold para dividir el dataset y evaluar el modelo. Aunque podríamos dividir el conjunto en un 80% train y un 20% train, valores típicos, se ha optado por utilizar K-fold ya que asegura que cada subconjunto o fold del dataset es utilizado tanto para el entrenamiento como para prueba en distintas iteraciones, lo que nos proporcionará una evaluación más robusta.
#Cargamos la librería caret
library(caret)
#Configuramos la validación cruzada K-fold
set.seed(123)
k <- 5
# Configurar K-fold Cross-Validation con probabilidades de clase
train_control <- trainControl(
method = "cv", # Cross-validation
number = k, # Número de pliegues (folds)
classProbs = TRUE, # Habilitar probabilidades de clase
summaryFunction = twoClassSummary, # Para métricas de clasificación binaria
savePredictions = "final" # Guardar las predicciones finales
)
2.2 Entrenamiento del modelo:
Cambiamos los niveles del factor “diagnosis” a nombres válidos, en este caso “M” para los tumores malignos, “B” para aquellos benignos.
# Entrenar el modelo de Random Forest utilizando K-fold cross-validation
set.seed(123)
#Vamos a convertir
data$diagnosis <- factor(data$diagnosis, levels = c(0, 1), labels = c("B", "M"))
2.2.1 Árbol de Decisión (CART):
El árbol de decisión es un modelo simple que divide los datos en segmentos basados en reglas de decisión. Es útil para clasificaciones donde las decisiones son lógicas y fáciles de entender
# Árbol de Decisión utilizando K-fold cross-validation
model_cart <- train(
diagnosis ~ ., # Usamos todas las variables predictoras
data = data,
method = "rpart", # Árbol de decisión (CART)
trControl = train_control, # Control de validación cruzada
metric = "ROC" # Evaluar utilizando AUC (Área bajo la curva ROC)
)
# Ver el resumen del modelo entrenado
print(model_cart)
CART
569 samples
30 predictor
2 classes: 'B', 'M'
No pre-processing
Resampling: Cross-Validated (5 fold)
Summary of sample sizes: 455, 456, 455, 454, 456
Resampling results across tuning parameters:
cp ROC Sens Spec
0.004716981 0.9430688 0.9410798 0.8911406
0.049528302 0.9119667 0.8990219 0.9105205
0.792452830 0.7258972 0.9464789 0.5053156
ROC was used to select the optimal model using the largest value.
The final value used for the model was cp = 0.004716981.
# Extraer la importancia de las variables
importance_cart <- varImp(model_cart, scale = FALSE)
importance_cart_df <- importance_cart$importance
importance_cart_df$variable <- rownames(importance_cart_df)
El primer modelo analizado es el Árbol de Decisión (CART). Este modelo utiliza un valor de complejidad de poda (cp) de 0.0047, que fue seleccionado como el mejor parámetro mediante validación cruzada.
La curva ROC del modelo es de 0.9362, lo que indica una alta capacidad para discriminar entre las dos clases.
La sensibilidad (capacidad del modelo para identificar correctamente las observaciones positivas) es de 0.9383, lo que sugiere que el modelo es muy eficiente para detectar las observaciones positivas, aunque algo menos efectivo que otros modelos en cuanto a la especificidad. De hecho, la especificidad (capacidad para identificar correctamente las observaciones negativas) es de 0.8960, lo que representa una leve caída respecto a la sensibilidad.
Este desempeño es sólido y equilibrado, pero no es el más alto entre los modelos evaluados.
<font size="2">2.2.2 Random Forest </font>
El Random Forest es un algoritmo que construye múltiples árboles de decisión y realiza una predicción agregando las predicciones de todos los árboles individuales. Es robusto ante el sobreajuste.
model_rf <- train(
diagnosis ~ ., # Usamos todas las variables predictoras
data = data,
method = "rf", # Random Forest
trControl = train_control, # Control de validación cruzada
metric = "ROC" # Evaluar utilizando AUC (Área bajo la curva ROC)
)
# Ver el resumen del modelo entrenado
print(model_rf)
Random Forest
569 samples
30 predictor
2 classes: 'B', 'M'
No pre-processing
Resampling: Cross-Validated (5 fold)
Summary of sample sizes: 454, 454, 456, 456, 456
Resampling results across tuning parameters:
mtry ROC Sens Spec
2 0.9926860 0.9803991 0.9198228
16 0.9910848 0.9831768 0.9341085
30 0.9900850 0.9719484 0.9341085
ROC was used to select the optimal model using the largest value.
The final value used for the model was mtry = 2.
# Extraer la importancia de las variables
importance_rf <- varImp(model_rf, scale = FALSE)
importance_rf_df <- importance_rf$importance
importance_rf_df$variable <- rownames(importance_rf_df)
Para entender mejor los resultados del modelo, aclarar que el parámetro mtry en el contexto de Random Forest es uno de los hiperparámetros clave que se utiliza para controlar el número de variables (características) que el modelo considera para dividir cada nodo en cada árbol del bosque. Específicamente, mtry define cuántas características serán elegidas aleatoriamente para cada nodo cuando se construye un árbol en el Random Forest.
Random Forest, muestra un desempeño destacable. Este modelo seleccionó el valor de mtry (número de variables aleatorias para cada división del árbol) igual a 2, lo que optimiza la capacidad de discriminación. Su curva ROC alcanza un valor impresionante de 0.9908, lo que es un indicador claro de su capacidad para separar las dos clases con gran precisión. Además, la sensibilidad de 0.9804 muestra que Random Forest tiene una excelente capacidad para detectar correctamente las observaciones positivas, y la especificidad de 0.9241 indica que también es eficaz en identificar las observaciones negativas. Este modelo sobresale por su alta precisión en ambos aspectos, lo que lo convierte en uno de los modelos más robustos y confiables para este conjunto de datos.
2.2.3 Support Vector Machine (SVM)
El Support Vector Machine (SVM) es un algoritmo que intenta encontrar un hiperplano que mejor separe las diferentes clases de datos.
#install.packages("kernlab")
library(kernlab)
model_svm <- train(
diagnosis ~ ., # Usamos todas las variables predictoras
data = data,
method = "svmRadial", # Support Vector Machine con kernel radial
trControl = train_control, # Control de validación cruzada
metric = "ROC" # Evaluar utilizando AUC (Área bajo la curva ROC)
)
# Ver el resumen del modelo entrenado
print(model_svm)
Support Vector Machines with Radial Basis Function Kernel
569 samples
30 predictor
2 classes: 'B', 'M'
No pre-processing
Resampling: Cross-Validated (5 fold)
Summary of sample sizes: 455, 454, 456, 456, 455
Resampling results across tuning parameters:
C ROC Sens Spec
0.25 0.9921417 0.9692488 0.9338870
0.50 0.9937241 0.9692879 0.9529347
1.00 0.9953659 0.9748826 0.9624585
Tuning parameter 'sigma' was held constant at a value of 0.04441149
ROC was used to select the optimal model using the largest value.
The final values used for the model were sigma = 0.04441149 and C = 1.
# Extraer la importancia de las variables
importance_svm <- varImp(model_svm, scale = FALSE)
importance_svm_df <- importance_svm$importance
importance_svm_df$variable <- rownames(importance_svm_df)
El tercer modelo evaluado es el de Máquinas de Soporte Vectorial con núcleo Radial (SVM). Este modelo, con un parámetro de regularización 𝐶=1 y un valor de sigma de 0.0475, mostró un desempeño excelente en términos de la curva ROC, alcanzando un valor de 0.9948, el más alto entre todos los modelos. Esta métrica refleja una capacidad de discriminación superior, lo que implica que el modelo tiene una alta habilidad para separar correctamente las clases ‘Negative’ y ‘Positive’. La sensibilidad de 0.9748 y la especificidad de 0.9670 también son notablemente altas, lo que sugiere que el modelo tiene un buen rendimiento tanto en la detección de las observaciones positivas como en la correcta identificación de las negativas. Sin embargo, es importante destacar que el modelo SVM presentó varias advertencias durante el proceso de optimización (warnings), relacionadas con problemas de convergencia y probabilidades extremas de 0 o 1. Esto podría indicar que el modelo podría estar sobreajustando o enfrentando dificultades para encontrar un equilibrio estable, lo que debe tenerse en cuenta al evaluar su estabilidad y generalización.
2.2.4 Regresión Logística
Este algoritmo es un modelo de clasificación que predice la probabilidad de que una observación pertenzca a una clase o no.
model_logit <- train(
diagnosis ~ ., # Usamos todas las variables predictoras
data = data,
method = "glm", # Regresión Logística
trControl = train_control, # Control de validación cruzada
metric = "ROC" # Evaluar utilizando AUC (Área bajo la curva ROC)
)
Warning: glm.fit: algorithm did not convergeWarning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: algorithm did not convergeWarning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: algorithm did not convergeWarning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: algorithm did not convergeWarning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: algorithm did not convergeWarning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: algorithm did not convergeWarning: glm.fit: fitted probabilities numerically 0 or 1 occurred
# Ver el resumen del modelo entrenado
print(model_logit)
Generalized Linear Model
569 samples
30 predictor
2 classes: 'B', 'M'
No pre-processing
Resampling: Cross-Validated (5 fold)
Summary of sample sizes: 455, 455, 456, 455, 455
Resampling results:
ROC Sens Spec
0.9527876 0.946831 0.9434109
# Extraer la importancia de las variables
importance_logit <- varImp(model_logit, scale = FALSE)
importance_logit_df <- importance_logit$importance
importance_logit_df$variable <- rownames(importance_logit_df)
El último modelo considerado es el Modelo Lineal Generalizado (GLM). Este modelo, a pesar de ser sencillo en su estructura, mostró un rendimiento respetable. Su curva ROC alcanzó un valor de 0.9552, lo cual es inferior a los de Random Forest y SVM, pero sigue siendo adecuado para tareas de clasificación. La sensibilidad de 0.9438 indica que el modelo tiene una buena capacidad para identificar las observaciones positivas, mientras que la especificidad de 0.9484 es ligeramente mejor que la sensibilidad, lo que sugiere que el modelo tiene un desempeño ligeramente mejor para detectar las observaciones negativas en comparación con las positivas. Aunque el modelo GLM es funcional, su rendimiento en términos de la curva ROC es algo inferior en comparación con los modelos más complejos como SVM y Random Forest.
2.3 Comparación de Modelos
# Comparar el rendimiento de los modelos
resamples <- resamples(list(cart = model_cart, rf = model_rf, svm = model_svm, logit = model_logit))
# Ver el resumen de la comparación entre los modelos
summary(resamples)
Call:
summary.resamples(object = resamples)
Models: cart, rf, svm, logit
Number of resamples: 5
ROC
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
cart 0.9299050 0.9330986 0.9357814 0.9430688 0.9467593 0.9697997 0
rf 0.9820736 0.9902750 0.9953052 0.9926860 0.9964470 0.9993293 0
svm 0.9874031 0.9947593 0.9976526 0.9953659 0.9976852 0.9993293 0
logit 0.9365079 0.9434982 0.9495701 0.9527876 0.9610219 0.9733400 0
Sens
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
cart 0.9014085 0.9295775 0.9577465 0.9410798 0.9583333 0.9583333 0
rf 0.9583333 0.9718310 0.9718310 0.9803991 1.0000000 1.0000000 0
svm 0.9305556 0.9718310 0.9859155 0.9748826 0.9861111 1.0000000 0
logit 0.9027778 0.9295775 0.9577465 0.9468310 0.9718310 0.9722222 0
Spec
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
cart 0.8571429 0.8571429 0.8809524 0.8911406 0.9069767 0.9534884 0
rf 0.8604651 0.8809524 0.9285714 0.9198228 0.9523810 0.9767442 0
svm 0.9302326 0.9534884 0.9761905 0.9624585 0.9761905 0.9761905 0
logit 0.9047619 0.9302326 0.9523810 0.9434109 0.9534884 0.9761905 0
# Visualización de la comparación
bwplot(resamples)
2.4 Estudio de la Importancia de las Variables
El estudio de la importancia de variables nos permite identificar cuáles son las características que más influyen en las predicciones realizadas. Esto es especialmente útil cuando trabjamos con datos de ámbito sanitario, ya que nos permite priorizar las variables más relevantes para realizar diagnósticos o tomar decisiones informadas.
El objetivo del estudio de importancia de variables es eliminar aquellas que consistentemente tienen baja importancia en todos los modelos:
1- Obtener la importancia de variables de cada modelo.
2.4.1 Estudio de la Importancia de las Variables con Árbol de Decisión (CART)
En los árboles de decisión, la importancia de vairables se calcula en función de las ganancias de reducción de impureza, por ejemplo, la reducción de la entropía o del índice Gini en los nodos donde la variables es utilizada para dividr los datos.
plot(importance_cart, main = "Importancia de Variables - CART")
2.4.2 Estudio de la Importancia de las Variables con Random Forest
En Random Forest la importancia se calcula mediante dos enfoques comunes:
plot(importance_rf, main = "Importancia de Variables - Random Forest")
2.4.3 Estudio de la Importancia de las Variables con Support Vector Machine (SVM)
El calculo de la importancia en SVM no es tan directo, ya que este modelo no se basa en una estructura jerárquica o en una gregación de árboles. Podemos estimar la importancia de las variables mediante análisis post-hoc, como la evaluación de los coeficientes en el espacio formado por el núcleo radial.
plot(importance_svm, main = "Importancia de Variables- SVM")
2.4.4 Estudio de la Importancia de las Variables con Regresión Logística
En regresión logística, la importancia de variables se puede analizar mediante los coeficientes estimados del modelo. Estos coeficientes indican la magnitud y la dirección del efecto de cada variable en la probabilidad de que un tumor sea maligno.
plot(importance_logit, main = "Importancia de Variables - GLM")
2.4.4 Comparación General del estudio de la Importancia de las Variables
2- Normalizar la importancia para compararla entre modelos. 3- Calcular una métrica consolidada de importancia promedio. 4- Identificar las variables con menor impacto en todos los modelos.
# Consolidar importancia de variables
importance_combined <- merge(
merge(importance_cart_df, importance_rf_df, by = "variable", suffixes = c("_cart", "_rf")),
merge(importance_svm_df, importance_logit_df, by = "variable", suffixes = c("_svm", "_logit")),
by = "variable"
)
# Promedio de importancia
importance_combined$mean_importance <- rowMeans(importance_combined[, -1], na.rm = TRUE)
# Seleccionar las menos importantes (por debajo de un umbral, por ejemplo, el percentil 20)
threshold <- quantile(importance_combined$mean_importance, 0.2)
least_important_vars <- importance_combined$variable[importance_combined$mean_importance <= threshold]
# Eliminar estas variables del dataset original
data_reduced <- data[, !(names(data) %in% least_important_vars)]
# Guardar el dataset reducido
write.csv(data_reduced, "data_reduced.csv", row.names = FALSE)
data_reduced
dim(data_reduced)
[1] 569 25
colnames(data_reduced)
[1] "diagnosis" "texture_mean" "perimeter_mean"
[4] "area_mean" "concavity_mean" "concave.points_mean"
[7] "symmetry_mean" "radius_se" "texture_se"
[10] "perimeter_se" "area_se" "smoothness_se"
[13] "compactness_se" "concavity_se" "concave.points_se"
[16] "symmetry_se" "fractal_dimension_se" "radius_worst"
[19] "texture_worst" "perimeter_worst" "area_worst"
[22] "compactness_worst" "concavity_worst" "concave.points_worst"
[25] "symmetry_worst"
colnames(data)
[1] "diagnosis" "radius_mean"
[3] "texture_mean" "perimeter_mean"
[5] "area_mean" "smoothness_mean"
[7] "compactness_mean" "concavity_mean"
[9] "concave.points_mean" "symmetry_mean"
[11] "fractal_dimension_mean" "radius_se"
[13] "texture_se" "perimeter_se"
[15] "area_se" "smoothness_se"
[17] "compactness_se" "concavity_se"
[19] "concave.points_se" "symmetry_se"
[21] "fractal_dimension_se" "radius_worst"
[23] "texture_worst" "perimeter_worst"
[25] "area_worst" "smoothness_worst"
[27] "compactness_worst" "concavity_worst"
[29] "concave.points_worst" "symmetry_worst"
[31] "fractal_dimension_worst"
El siguiente paso será repetir nuestro análisis supervisado esta vez usando el dataset reducido en el que no aparecen las columnas “menos importantes”. De esta forma podremos analizar si los resultados mejorar, empeoran o no afectan con el estudio de importancia de varianles:
3. Análisis Supervisado con dataset reducido. 3.1 Árbol de Decisión (CART)
# Árbol de Decisión utilizando K-fold cross-validation
model_cart <- train(
diagnosis ~ ., # Usamos todas las variables predictoras
data = data_reduced,
method = "rpart", # Árbol de decisión (CART)
trControl = train_control, # Control de validación cruzada
metric = "ROC" # Evaluar utilizando AUC (Área bajo la curva ROC)
)
# Ver el resumen del modelo entrenado
print(model_cart)
CART
569 samples
24 predictor
2 classes: 'B', 'M'
No pre-processing
Resampling: Cross-Validated (5 fold)
Summary of sample sizes: 455, 456, 456, 454, 455
Resampling results across tuning parameters:
cp ROC Sens Spec
0.004716981 0.9450967 0.9523474 0.8914729
0.049528302 0.8894831 0.9298513 0.8535991
0.792452830 0.7976289 0.9467527 0.6485050
ROC was used to select the optimal model using the largest value.
The final value used for the model was cp = 0.004716981.
3.2 Random Forest
model_rf <- train(
diagnosis ~ ., # Usamos todas las variables predictoras
data = data_reduced,
method = "rf", # Random Forest
trControl = train_control, # Control de validación cruzada
metric = "ROC" # Evaluar utilizando AUC (Área bajo la curva ROC)
)
# Ver el resumen del modelo entrenado
print(model_rf)
Random Forest
569 samples
24 predictor
2 classes: 'B', 'M'
No pre-processing
Resampling: Cross-Validated (5 fold)
Summary of sample sizes: 455, 454, 455, 456, 456
Resampling results across tuning parameters:
mtry ROC Sens Spec
2 0.9908968 0.9690923 0.9295681
13 0.9876974 0.9634194 0.9387597
24 0.9864531 0.9577856 0.9293466
ROC was used to select the optimal model using the largest value.
The final value used for the model was mtry = 2.
3.3 Support Vector Machine (SVM)
#install.packages("kernlab")
library(kernlab)
model_svm <- train(
diagnosis ~ ., # Usamos todas las variables predictoras
data = data_reduced,
method = "svmRadial", # Support Vector Machine con kernel radial
trControl = train_control, # Control de validación cruzada
metric = "ROC" # Evaluar utilizando AUC (Área bajo la curva ROC)
)
# Ver el resumen del modelo entrenado
print(model_svm)
Support Vector Machines with Radial Basis Function Kernel
569 samples
24 predictor
2 classes: 'B', 'M'
No pre-processing
Resampling: Cross-Validated (5 fold)
Summary of sample sizes: 454, 456, 455, 456, 455
Resampling results across tuning parameters:
C ROC Sens Spec
0.25 0.9913841 0.9663928 0.9478405
0.50 0.9938446 0.9692097 0.9621262
1.00 0.9946373 0.9692097 0.9621262
Tuning parameter 'sigma' was held constant at a value of 0.05850564
ROC was used to select the optimal model using the largest value.
The final values used for the model were sigma = 0.05850564 and C = 1.
3.4 Regresión Logística
model_logit <- train(
diagnosis ~ ., # Usamos todas las variables predictoras
data = data_reduced,
method = "glm", # Regresión Logística
trControl = train_control, # Control de validación cruzada
metric = "ROC" # Evaluar utilizando AUC (Área bajo la curva ROC)
)
Warning: glm.fit: algorithm did not convergeWarning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: algorithm did not convergeWarning: glm.fit: fitted probabilities numerically 0 or 1 occurredWarning: glm.fit: fitted probabilities numerically 0 or 1 occurred
# Ver el resumen del modelo entrenado
print(model_logit)
Generalized Linear Model
569 samples
24 predictor
2 classes: 'B', 'M'
No pre-processing
Resampling: Cross-Validated (5 fold)
Summary of sample sizes: 455, 454, 455, 456, 456
Resampling results:
ROC Sens Spec
0.975885 0.9662754 0.9579181
3.5 Comparación de resultados análisis supervisado dataset incial vs dataset reducido:
# Crear los datos para cada modelo en formato de data frame
# Dataset inicial
inicial <- data.frame(
Modelo = c("CART", "Random Forest", "SVM", "GLM"),
ROC = c(0.9431, 0.9927, 0.9954, 0.9528),
Sens = c(0.9411, 0.9804, 0.9749, 0.9468),
Spec = c(0.8911, 0.9198, 0.9625, 0.9434),
Dataset = "Inicial"
)
# Dataset reducido
reducido <- data.frame(
Modelo = c("CART", "Random Forest", "SVM", "GLM"),
ROC = c(0.9451, 0.9909, 0.9946, 0.9759),
Sens = c(0.9523, 0.9691, 0.9692, 0.9663),
Spec = c(0.8915, 0.9296, 0.9621, 0.9579),
Dataset = "Reducido"
)
# Unir ambos datasets en una sola tabla por el modelo
comparativa <- merge(inicial, reducido, by = "Modelo")
# Imprimir la tabla en consola
print(comparativa)
NA
# Instalar flextable si no está instalado
install.packages("flextable")
WARNING: Rtools is required to build R packages but is not currently installed. Please download and install the appropriate version of Rtools before proceeding:
https://cran.rstudio.com/bin/windows/Rtools/
Installing package into ‘C:/Users/hered/AppData/Local/R/win-library/4.4’
(as ‘lib’ is unspecified)
also installing the dependencies ‘fontBitstreamVera’, ‘fontLiberation’, ‘fontquiver’, ‘zip’, ‘gdtools’, ‘officer’
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.4/fontBitstreamVera_0.1.1.zip'
Content type 'application/zip' length 697821 bytes (681 KB)
downloaded 681 KB
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.4/fontLiberation_0.1.0.zip'
Content type 'application/zip' length 4530297 bytes (4.3 MB)
downloaded 4.3 MB
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.4/fontquiver_0.2.1.zip'
Content type 'application/zip' length 2279308 bytes (2.2 MB)
downloaded 2.2 MB
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.4/zip_2.3.1.zip'
Content type 'application/zip' length 434674 bytes (424 KB)
downloaded 424 KB
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.4/gdtools_0.4.1.zip'
Content type 'application/zip' length 2207516 bytes (2.1 MB)
downloaded 2.1 MB
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.4/officer_0.6.7.zip'
Content type 'application/zip' length 1839413 bytes (1.8 MB)
downloaded 1.8 MB
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.4/flextable_0.9.7.zip'
Content type 'application/zip' length 2357184 bytes (2.2 MB)
downloaded 2.2 MB
package ‘fontBitstreamVera’ successfully unpacked and MD5 sums checked
package ‘fontLiberation’ successfully unpacked and MD5 sums checked
package ‘fontquiver’ successfully unpacked and MD5 sums checked
package ‘zip’ successfully unpacked and MD5 sums checked
package ‘gdtools’ successfully unpacked and MD5 sums checked
package ‘officer’ successfully unpacked and MD5 sums checked
package ‘flextable’ successfully unpacked and MD5 sums checked
The downloaded binary packages are in
C:\Users\hered\AppData\Local\Temp\RtmpWuj0fH\downloaded_packages
# Cargar la librería
library(flextable)
Warning: package ‘flextable’ was built under R version 4.4.2
Attaching package: ‘flextable’
The following object is masked from ‘package:purrr’:
compose
# Crear la tabla con flextable
tabla_flex <- flextable(comparativa)
# Imprimir la tabla en un entorno que soporte flextable (por ejemplo, RStudio)
tabla_flex
Modelo | ROC.x | Sens.x | Spec.x | Dataset.x | ROC.y | Sens.y | Spec.y | Dataset.y |
|---|---|---|---|---|---|---|---|---|
CART | 0.9431 | 0.9411 | 0.8911 | Inicial | 0.9451 | 0.9523 | 0.8915 | Reducido |
GLM | 0.9528 | 0.9468 | 0.9434 | Inicial | 0.9759 | 0.9663 | 0.9579 | Reducido |
Random Forest | 0.9927 | 0.9804 | 0.9198 | Inicial | 0.9909 | 0.9691 | 0.9296 | Reducido |
SVM | 0.9954 | 0.9749 | 0.9625 | Inicial | 0.9946 | 0.9692 | 0.9621 | Reducido |
NA
La sensibilidad mejora con el dataset reducido, lo que indica una mejor capacidad para detectar casos positivos. Esto puede deberse a que las variables menos relevantes generaban divisiones innecesarias en el árbol.
Aunque hay una leve disminución en ROC y sensibilidad, la especificidad mejora. Esto implica que el modelo con el dataset reducido identifica mejor los casos negativos.
El impacto del dataset reducido es mínimo, pero muestra una ligera disminución en todas las métricas. Esto sugiere que las variables eliminadas tenían una influencia menor, pero no eran completamente irrelevantes.
El modelo con dataset reducido muestra mejoras claras en todas las métricas, lo que refuerza la idea de que las variables eliminadas no eran útiles para este enfoque lineal.
En resumen: La eliminación de variables menos importantes mantiene un rendimiento comparable, e incluso mejora algunas métricas específicas en ciertos modelos.
Esto sugiere que las variables eliminadas no aportaban información relevante o incluso podían introducir ruido.
Reducir el número de variables tiene ventajas prácticas, como menores requerimientos computacionales y mayor interpretabilidad, sin comprometer el rendimiento.
4. Análisis No Supervisado.
El análisis no supervisado se centra en explorar patrones o estructuras en los datos sin utlizar etiquetas o clases. Para éste análisis, se utulizarán técnicas como clustering o reducción de dimensionalidad.
4.1 Preparación del Dataset
El primer paso será eliminar la columna “diagnosis” y normalizar el dataset, ya que muchas técnicas de clustering y reducción de dimensionalidad son sensibles a las escalas de las variables.
data_UnSupervised <- data %>% select(-diagnosis)
head(data_UnSupervised)
data_UnSupervised$diagnosis
NULL
# Normalizar los datos (sin la variable de respuesta si existe)
preprocess_params <- preProcess(data_UnSupervised[, -ncol(data_UnSupervised)], method = c("center", "scale"))
data_normalized <- predict(preprocess_params, data_UnSupervised[, -ncol(data_UnSupervised)])
4.2 Clustering: Determinación del Número Óptimo de Clusters
Para ello utilizaremos el método del codo y el índice silhouette:
# Método del codo
wss <- (nrow(data_normalized) - 1) * sum(apply(data_normalized, 2, var))
for (i in 2:15) wss[i] <- sum(kmeans(data_normalized, centers = i)$withinss)
plot(1:15, wss, type = "b", xlab = "Número de Clusters", ylab = "Suma de Cuadrados Internos")
# Índice silhouette
library(cluster)
silhouette_scores <- numeric()
for (i in 2:15) {
km <- kmeans(data_normalized, centers = i)
silhouette_scores[i] <- mean(silhouette(km$cluster, dist(data_normalized))[, 3])
}
plot(2:15, silhouette_scores[-1], type = "b", xlab = "Número de Clusters", ylab = "Puntaje de Silhouette")
4.3 K-Means
Una vez determinado el número óptimo de clusters, se ejecuta K-means:
set.seed(123)
optimal_clusters = 4
kmeans_model <- kmeans(data_normalized, centers = optimal_clusters, nstart = 25)
# Agregar etiquetas de cluster al dataset original
data$cluster <- kmeans_model$cluster
4.4 PCA
El próximo paso de nuestro análisis no supervisado será utilizar el Análisis de Componentes Principales para visualizar la estructura de los datos y validar los resultados del clustering.
# PCA
pca_model <- prcomp(data_normalized, scale = TRUE)
# Visualización de los dos primeros componentes principales
library(ggplot2)
pca_data <- data.frame(pca_model$x[, 1:2], cluster = as.factor(data$cluster))
ggplot(pca_data, aes(x = PC1, y = PC2, color = cluster)) +
geom_point() +
theme_minimal() +
labs(title = "Clustering visualizado en espacio PCA")
Se visualizan claramente cuatro clústeres.
Los clústeres están bien definidos y separados espacialmente, especialmente entre el clúster púrpura y los demás.
Hay un grado menor de solapamiento entre los clústeres, lo que sugiere que las variables originales ofrecen una separación más clara entre las clases en este espacio. Respecto al espacio de Variación, los puntos están distribuidos en torno a coordenadas más centradas (de -15 a 5 en PC1 y -10 a 10 en PC2).
Vamos también a repetir el clustering con el dataset reducido generado durante el estudio de la importancia de las variables:
4.5 Análisis No Supervisado con Dataset Reducido según importancia de variables
data_UnSupervised_reduced <- data_reduced %>% select(-diagnosis)
head(data_UnSupervised_reduced)
data_UnSupervised_reduced$diagnosis
NULL
# Normalizar los datos (sin la variable de respuesta si existe)
preprocess_params <- preProcess(data_UnSupervised_reduced[, -ncol(data_UnSupervised_reduced)], method = c("center", "scale"))
data_normalized_reduced <- predict(preprocess_params, data_UnSupervised_reduced[, -ncol(data_UnSupervised_reduced)])
# Método del codo
wss <- (nrow(data_normalized_reduced) - 1) * sum(apply(data_normalized_reduced, 2, var))
for (i in 2:15) wss[i] <- sum(kmeans(data_normalized_reduced, centers = i)$withinss)
plot(1:15, wss, type = "b", xlab = "Número de Clusters", ylab = "Suma de Cuadrados Internos")
# Índice silhouette
library(cluster)
silhouette_scores <- numeric()
for (i in 2:15) {
km <- kmeans(data_normalized_reduced, centers = i)
silhouette_scores[i] <- mean(silhouette(km$cluster, dist(data_normalized_reduced))[, 3])
}
plot(2:15, silhouette_scores[-1], type = "b", xlab = "Número de Clusters", ylab = "Puntaje de Silhouette")
k-means
set.seed(123)
optimal_clusters_2 = 5
kmeans_model_reduced <- kmeans(data_normalized_reduced, centers = optimal_clusters_2, nstart = 25)
# Agregar etiquetas de cluster al dataset original
data_reduced$cluster <- kmeans_model_reduced$cluster
# PCA
pca_model_reduced <- prcomp(data_normalized_reduced, scale = TRUE)
# Visualización de los dos primeros componentes principales
library(ggplot2)
pca_data_reduced <- data.frame(pca_model_reduced$x[, 1:2], cluster = as.factor(data_reduced$cluster))
ggplot(pca_data_reduced, aes(x = PC1, y = PC2, color = cluster)) +
geom_point() +
theme_minimal() +
labs(title = "Clustering visualizado en espacio PCA")
En este caso, se visualizan cinco clústeres, lo que podría indicar una mayor sensibilidad del modelo al agrupar los datos reducidos.
Los clústeres están más compactos en el centro del gráfico, con menos separación clara en comparación con la versión completa.
Existen clústeres adicionales (como el clúster 5, representado en rosa), lo que puede indicar una partición más forzada o sensibilidad al ruido.
Hay un mayor solapamiento entre los clústeres en comparación con el gráfico sin reducción de datos.
El rango del eje PC1 varía de -5 a 15, mientras que PC2 tiene una distribución de -5 a 15, lo que sugiere un cambio en la importancia relativa de los componentes principales después de la reducción de variables.
En conclusión, sin reducción, los clústeres son más definidos y separados, lo que sugiere que las variables eliminadas contienen información relevante para distinguir los grupos.
Con reducción, los clústeres muestran mayor solapamiento y parecen menos definidos, posiblemente debido a la pérdida de información al reducir las variables.
La aparición de un quinto clúster en el conjunto reducido indica que la reducción puede introducir sensibilidad al ruido o revelar patrones más complejos (aunque menos significativos).